/**
* @projectDescription X-Cube: Ajax Library
* @author Julio Greff
* @version 0.3
*/

/**
* XCube Class
* Contains the main methods to manipulate Ajax requests
* @param {String} url URL to be requested
* @param {Object} options Optional, object with options
* @return {Object} X-Cube object
*/
var XCube = function(url, options) {
    return this.initialize(url, options)
}

XCube.prototype.initialize = function(url, options) {
    this.url = new Url(url)
    this.options = {
		"async": true,
		"autoCancel": false,
		"browserCache": false,
    "cacheLength": 10,
		"method": "get",
    "onCancel": false,
		"onComplete": false,
		"onFailure": false,
		"onInteractive": false,
		"onOpened": false,
		"onRequest": false,
		"onSending": false,
		"update": false
    }
    this.lastRequest = {
		"data": "",
		"responseText": "",
		"responseXml": "",
		"status": "",
		"statusText": ""
    }
    this.headers = {}
    this.queue = new Queue()
    this.setTransport().setOptions(options).setHeader("X-Requested-With", "XMLHttpRequest")
    this.cache = new BrowserCache(this.options.cacheLength)
    return this
}

XCube.prototype.setHeader = function(header, value) {
    this.headers[header] = value
    return this
}

XCube.prototype.setHeaders = function() {
    for(var header in this.headers) {
        this.XmlHttp.setRequestHeader(header, this.headers[header])
    }
    return this
}

XCube.prototype.setOptions = function(options) {
    for(var option in options) {
        this.setOption(option, options[option])
    }
    return this
}

XCube.prototype.setOption = function(option, value) {
    this.options[option] = value
    return this
}

XCube.prototype.setTransport = function() {
    this.XmlHttp = (window.XMLHttpRequest) ? new XMLHttpRequest() : (ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : false)
    return this
}

XCube.prototype.fireEvent = function(event, args) {
    if(typeof this.options["on" + event] == "function") {
        this.options["on" + event].apply(this, args)
    }
    return this
}

XCube.prototype.onStateChange = function() {
    var event, args = []
    switch(this.XmlHttp.readyState) {
		case 4:
			this.onComplete()
			if(this.XmlHttp.status == 200) {
				event = "Complete"
				args.push(this.lastRequest.responseText, this.lastRequest.responseXml)
			}
			else {
				event = "Failure"
				args.push(this.lastRequest.status, this.lastRequest.statusText)
			}
			break
		case 3:
			event = "Interactive"
			break
		case 2:
			event = "Sending"
			break
		case 1:
			event = "Opened"
			break
    }
    this.fireEvent(event, args)
    return this
}

XCube.prototype.onComplete = function(cache) {
	var request = cache ? cache : this.XmlHttp
	this.lastRequest = {
		"data": this.lastRequest.data,
		"responseText": request.responseText,
		"responseXML": request.responseXML,
		"status": request.status,
		"statusText": request.statusText
	}
	if(this.options.browserCache) {
		this.cache.store(this.lastRequest.data, this.lastRequest)
	}
	if(this.options.update) {
		element = typeof this.options.update == "string" ? document.getElementById(this.options.update) : this.options.update
		element.innerHTML = this.lastRequest.responseText
	}
	this.isRunning = false
    setTimeout(this.queue.execute.bind(this.queue), 100)
	return this
}

XCube.prototype.request = function(data) {
	if(this.options.autoCancel || !this.isRunning) {
        var url = this.url.source
		if((this.options.method = this.options.method.toLowerCase()) == "post") {
            this.setHeader("Content-Type", "application/x-www-form-urlencoded")
            data = data || ""
		}
		else {
            url += this.url.addParam(data)
			data = null
		}
	    this.lastRequest.data = data || url
		this.stop()
		this.isRunning = true
        this.fireEvent("Request")
		if(this.options.browserCache && (cache = this.cache.retrieve(this.lastRequest.data))) {
			this.onComplete(cache).fireEvent("Complete", [this.lastRequest.responseText, this.lastRequest.responseXML])
			return this
		}
		this.XmlHttp.open(this.options.method, url, this.options.async)
		this.XmlHttp.onreadystatechange = this.onStateChange.bind(this)
		this.setHeaders()
		this.XmlHttp.send(data)
		if(!this.options.async) {
			this.onComplete().fireEvent("Complete", [this.lastRequest.responseText, this.lastRequest.responseXML])
		}
	}
    else if(!this.options.autoCancel && this.isRunning) {
        this.queue.add(this.request.bind(this, data))
    }
    return this
}

XCube.prototype.stop = function() {
    if(this.isRunning == true) {
		this.isRunning = false
		this.XmlHttp.abort()
		this.XmlHttp.onreadystatechange = function(){}
        this.fireEvent("Cancel")
		this.setXmlHttp()
    }
    return this
}

XCube.request = function(url, update, options) {
    new XCube(url, options).setOption("update", update).request()
    return true
}


var Queue = function() {
    this.queue = []
    return this.initialize()
}

Queue.prototype.initialize = function() {
    this.queue = []
    return this
}

Queue.prototype.execute = function() {
    if(this.queue.length > 0) {
        this.queue.shift()()
    }
    return this
}

Queue.prototype.add = function(fn) {
    if(fn) {
        this.queue.push(fn)
    }
    return this
}

Queue.prototype.clear = function() {
    this.queue = []
    return this
}


var BrowserCache = function(maxLength) {
    return this.initialize(maxLength)
}

BrowserCache.prototype.initialize = function(maxLength) {
    this.stack = {}
    this.mru = []
    this.maxLength = maxLength || 1
    return this
}

BrowserCache.prototype.store = function(key, value) {
    var key = escape(key)
    this.stack[key] = value
    if(this.mru.indexOf(key) > -1) {
        this.remove(key)
    }
    this.mru.push(key)
    this.checkLength()
    return this
}

BrowserCache.prototype.retrieve = function(key) {
    var key = escape(key)
    this.remove(key)
    this.mru.push(key)
    return this.stack[key]
}

BrowserCache.prototype.remove = function(key) {
    this.mru.splice(this.mru.indexOf(key), 1)
    return this    
}

BrowserCache.prototype.clear = function() {
    this.stack = {}
    this.mru = []
    return this
}

BrowserCache.prototype.checkLength = function() {
    if(this.mru.length > this.maxLength) {
        delete this.stack[this.mru.shift()]
    }
    return this
}

var Url = function(url) {
    return this.initialize(url)
}

Url.prototype.initialize = function(url) {
    this.parse(url)
    return this
}

Url.prototype.parse = function(url) {
    // URL parser based in parseUri 1.2 <http://blog.stevenlevithan.com/archives/parseuri> by Steven Levithan
    var parser = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/
    var keys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"]
    var parsedUrl = parser.exec(url)
    for(var i in keys) {
        this[keys[i]] = parsedUrl[i]
    }
    return this
}

Url.prototype.addParam = function(param) {
    if(this.query) {
        var query = "&" + param
    }
    else {
        var query = param
    }
    return query
}


var History = function() {
    return this.initialize()
}

History.prototype.initialize = function() {
    return this
}

History.prototype.register = function() {
    return this
}

History.prototype.check = function() {
    return true
}

/*** Utilitary Functions ***/

/**
* Binds a function to a determined scope
* @param {Function} scope Scope
* @param {Object} arguments Arguments
* @return {Function} Function bind
*/
Function.prototype.bind = function() {
    var self = this, args = $A(arguments), scope = args.shift()
    return function() {
        return self.apply(scope, args.concat($A(arguments)))
    }
}

Array.prototype.indexOf = function(item) {
    for(var i in this) {
        if(this[i] == item) {
            return i
        }
    }
    return -1
}

/**
* Transforms enumerable objects into arrays
* @param {Object} object Object to be transformed
* @return {Array} Array
*/
var $A = function(object) {
    var array = []
    for(var i = 0; i < object.length; i++) {
        array[i] = object[i]
    }
    return array
}